"""
参考地址1：https://www.cnblogs.com/liqianxin/p/12559863.html
参考地址2：https://zhuanlan.zhihu.com/p/104392764
参考地址3：https://blog.csdn.net/weixin_40318474/article/details/111797507
    1、可迭代对象
    2、迭代器（迭代对象，通过yield进行返回）
    3、生成器

生成器和迭代器的区别：
    1、迭代器是一个更抽象的概念，任何对象，如果它的类有 next 方法和 iter 方法返回自己本身，对于 string、list、
dict、tuple 等这类容器对象，使用 for 循环遍历是很方便的。在后台 for 语句对容器对象调用 iter()函数，iter()
是 python 的内置函数。iter()会返回一个定义了 next()方法的迭代器对象，它在容器中逐个访问容器内元素，next()
也是 python 的内置函数。在没有后续元素时，next()会抛出一个 StopIteration 异常。


    2、生成器（Generator）是创建迭代器的简单而强大的工具。它们写起来就像是正规的函数，只是在需要返回数
据的时候使用 yield 语句。每次 next()被调用时，生成器会返回它脱离的位置（它记忆语句最后一次执行的位置
和所有的数据值）

    区别：
        1、生成器能做到的迭代器能做的所有事，而且因为自动创建了iter()和next()方法，生成器显得特别简洁
        2、而且生成器也是高效的，使用生成器表达式取代列表解析可以同时同时节省内存。除了创建和保存程序状态的自动方法
        3、当生成器中间诶是，还会自动抛出StopIteration异常

装饰器函数有什么作用？装饰器函数和普通函数有什么区别？
    作用：在不改变函数内部代码和调用方式的基础上，给函数添加其他功能
    区别：在函数的定义上多了@装饰器函数
带固定参数和不固定参数的额装饰器有什么区别？
    区别：带固定参数的只能接收固定数量的参数个数，不定参数的可以接受多个不定数量的函数，可扩展性强

浅拷贝和深拷贝的区别？
    1、当数据类型是不可变的
        1）浅拷贝和深拷贝都是对源数据的引用，当拷贝对象的值发生变化之后，对源对象不会产生任何影响
    2、当数据类型是可变的
        1）浅拷贝只对源数据的第一层进行完全的复制，创建一块新的内存空间进行村存储，第二层以上的不进行复制，所以第一层的数据的改变相互不会影响，第二层的改变会相互之间影响
        2）深拷贝是对源数据的完成复制，不管源数据是几层嵌套，都是开辟一块新的空间来存储。相互之间不会影响

全局变量是否一定要是用global进行声明？
    在调用全局变量的时候，可以不用global进行声明，在修改全局变量的时候，必须用global进行声明

生成器和迭代器的关系？
    生成器是特殊的迭代器，生成器自动实现了__iter__()和__next__()方法，不在需要手动去实现，生成器在迭代的过程中可以修改迭代的值，而普通的迭代器在迭代的过程中如果修改迭代的值会发生异常
    所以生成器一定是迭代器，迭代器不一定是生成器

yield关键字有什么好处？
    可用于实现生成器

yield和return关键字的关系和区别？
    在函数中，遇到return就会跳出函数，除非下次调用，否则不会回去；
    yield则可以保存这一次的运行结果，跳出函数去先做别的时候，直到send()或next()触发回到yield跳出来的地方，继续执行

简单描述一下yield生成器函数的执行步骤？
    生成器每次调用next()开始执行，遇到yield返回，再次调用时从上次返回的yield语句处继续执行

生成器函数访问方式有哪几种？生成器函数中的send()有什么作用？
for循环、next()、send()
send(i)：调用yield同时给yield传值i

Python中递归的最大次数？
    999
递归函数停止的条件？
    不满足递归函数中设置的条件即会一次返回


"""

"""
1.1、可迭代对象
    for循环其实就是对可迭代对象的循环，属于可迭代对象的数据类型有：字符串、列表、字典、集合、元祖，当然我们学过的文件对象也是一种可迭代对象
"""
# 可迭代对象
s = "字符串"
print(s.__iter__())  # 含有__iter__的内置方法就是可迭代对象


"""
1.2、迭代器对象
    什么是迭代器对象？内置有__iter__/__next__的方法的对象都是迭代器对象，迭代器对象可以通过可迭代对象得到
    备注：迭代器对象一旦被取完，就会“死亡”，再想重新取值的话，就必须重新赋值然后重新取
"""
# 可迭代对象转为迭代器对象的方法
s = "字符串"
s_iter = s.__iter__()  # 此时的s_iter含有__next__的内置方法，就转为了迭代器对象。
print(s.__iter__())
# __next__:从头一次取出迭代器对象中的值。
print(s_iter.__next__())
print(s_iter.__next__())
print(s_iter.__next__())
# 如果迭代器对象中的值被取完之后依然继续取，就会报错。

"""
1.3、可迭代对象和迭代器的区别
    所有的迭代器队形都是可迭代队形，但是不是所有的可迭代对象都是迭代器对象。
    可迭代对象用iter之后会转化为迭代器对象，迭代器对象用iter转化依然是迭代器对象本身
"""

"""
1.4、for循环的作用机制
    可以看出来，while循环也可取出字典值，但是比较麻烦，我们经常用到for循环更加的方便，那么for循环的工作机制是怎样的呢？
"""
# while循环取字典的值。
dic = {"1":2,"2":2}
dic_iter = dic.__iter__()

while True:
    try:
        print(dic_iter.__next__())
    except StopIteration:
        break

# 实际上for循环就是迭代器循环
for k in dic:
    print(k)

# 以上述为例。
# 第一步：首先会进行dic = dic.__iter__(),将可迭代对象转化为迭代器对象
# 第二步：进行dic.__next__的调用，得到返回值给k，然后进行代码块的操作
# 第三步：循环第二步，直到出现StopIteration错误，对错误进行捕捉，退出循环。

"""
1.5、迭代器的优点和缺点
    1）优点
        1、为序列和非序列提供了一个统一的迭代取值方式
        2、惰性计算：不管迭代对象有多大，同一时刻只有一行数据存在
    2）缺点
        1、在取的时候我们并不知道这个迭代器的长度
        2.、取值是一次性的，过去的就让它过去，永远无法回来，除非我们定义一个新的迭代器对象
"""

"""
二、生成器

大白话：生成器就是自定义的迭代器
生成器本身就含有iter和next的内置方法，它本身就是迭代器，那么怎么定义一个迭代器呢？那就需要用到yield关键字了，yield有以下作用
    1、yield可以暂停函数的运行，不像return，可以让函数处于运行状态且不执行代码
    2、yield可以返回值，类似于return，其值就是生成器对象
"""


# next()的效果和.__next__()是一样的。
# 当生成器遇到next（）的调用开始运行，遇到yield停止执行代码，返回生成器对象，等待下次next。
def func():
    try:
        print(11111)
        yield 11111
        print(22222)
        yield 22222
    except Exception as e:
        print(e)

func()  # 此刻这个调用方式已经不好使了
a = func()  # 先弄一个生成器出来
b = next(a)  # 开始执行代码打印1111，在yield处暂停执行,返回11111
c = next(a)  # 继续执行2222,在yield暂停执行，返回22222
d = next(a)  # 函数体代码执行完毕，没有返回值，抛出StopIteration异常结束。


"""
Tips:
    如若不先根据函数造一个生成器对象，即a=func().一直使用next(func())就是在使用一个新的生成器，永远只执行第一个yied。
    yield不止可以返回值，他还可以从外界接受值
"""
def foo():
    print("生成器开始运行了。")
    while True:
        x = yield 123  # yield表达式
        print(f"{x}在运行着")
        y = yield 123  # yield表达式
        print(f"{y}在运行着11111111")


g = foo()  # 创造一个生成器对象
g.send(None)  # 等同于next(g) 第一步必须向生成器传一个空值启动生成器。（必须传）
test_x = g.send(5)  # 将5给变量值x,然后开始运行，直到下一个yield，然后返回yield之后的值
test_y = g.send(8)